/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef PLUGINS_ECMASCRIPT_COMPILER_OPTIMIZER_IR_DYN_DATATYPE_H
#define PLUGINS_ECMASCRIPT_COMPILER_OPTIMIZER_IR_DYN_DATATYPE_H

#include <optional>
#include "compiler/optimizer/ir/datatype.h"
#include "ir-dyn-base-types.h"
#include "profiling/profiling.h"

namespace ark::compiler::ecmascript {
static inline ark::compiler::AnyBaseType NumericDataTypeToAnyType(ark::compiler::DataType::Type type)
{
    switch (type) {
        case ark::compiler::DataType::Type::INT8:
        case ark::compiler::DataType::Type::UINT8:
        case ark::compiler::DataType::Type::INT16:
        case ark::compiler::DataType::Type::UINT16:
        case ark::compiler::DataType::Type::INT32:
            return ark::compiler::AnyBaseType::ECMASCRIPT_INT_TYPE;
        case ark::compiler::DataType::Type::UINT32:
        case ark::compiler::DataType::Type::INT64:
        case ark::compiler::DataType::Type::UINT64:
        case ark::compiler::DataType::Type::FLOAT32:
            // There is no direct method of boxing these types. Cast to f64 is an
            // option, but for now we miss a mechanism to signal that the cast
            // is needed. NOTE(asoldatov): Implement when this becomes an issue.
            return ark::compiler::AnyBaseType::UNDEFINED_TYPE;
        case ark::compiler::DataType::Type::FLOAT64:
            return ark::compiler::AnyBaseType::ECMASCRIPT_DOUBLE_TYPE;
        default:
            UNREACHABLE();
            return ark::compiler::AnyBaseType::UNDEFINED_TYPE;
    }
}

static inline ark::compiler::AnyBaseType GetAnyStringType()
{
    return ark::compiler::AnyBaseType::ECMASCRIPT_STRING_TYPE;
}

static inline ark::compiler::AnyBaseType GetAnyBigintType()
{
    return ark::compiler::AnyBaseType::ECMASCRIPT_BIGINT_TYPE;
}

// For two types (at least one of which is int or double) inclusion relation on
// masks returned by this function is eqivalent to this relation on types
static inline uint8_t GetPossibleTypeMask(ark::compiler::AnyBaseType type, profiling::AnyInputType allowedTypes)
{
    enum Type : uint8_t {
        NONE = 0,
        INTEGER = (1U << 0U),
        DOUBLE = (1U << 1U),
        BOOLEAN = (1U << 2U),
        NULL_TYPE = (1U << 3U),
        UNDEFINED = (1U << 4U),
        OTHER = (1U << 5U),
        // NOLINTNEXTLINE(hicpp-signed-bitwise)
        SPECIAL_INT = BOOLEAN | NULL_TYPE | INTEGER,
        SPECIAL_DOUBLE = UNDEFINED | DOUBLE,
        NUMBER = INTEGER | DOUBLE,
        SPECIAL_NUMBER = SPECIAL_INT | SPECIAL_DOUBLE
    };
    switch (type) {
        case ark::compiler::AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE:
            return Type::BOOLEAN;
        case ark::compiler::AnyBaseType::ECMASCRIPT_NULL_TYPE:
            return Type::NULL_TYPE;
        case ark::compiler::AnyBaseType::ECMASCRIPT_UNDEFINED_TYPE:
            return Type::UNDEFINED;
        case ark::compiler::AnyBaseType::ECMASCRIPT_INT_TYPE:
            if ((allowedTypes & profiling::AnyInputType::SPECIAL) != 0) {
                return Type::SPECIAL_INT;
            }
            return Type::INTEGER;
        case ark::compiler::AnyBaseType::ECMASCRIPT_DOUBLE_TYPE:
            if ((allowedTypes & profiling::AnyInputType::INTEGER) != 0) {
                if ((allowedTypes & profiling::AnyInputType::SPECIAL) != 0) {
                    return Type::SPECIAL_NUMBER;
                }
                return Type::NUMBER;
            }
            if ((allowedTypes & profiling::AnyInputType::SPECIAL) != 0) {
                return Type::SPECIAL_DOUBLE;
            }
            return Type::DOUBLE;
        default:
            return Type::OTHER;
    }
}

static inline std::optional<bool> IsAnyTypeCanBeSubtypeOf(ark::compiler::AnyBaseType superType,
                                                          ark::compiler::AnyBaseType type,
                                                          [[maybe_unused]] profiling::AnyInputType superAllowedTypes,
                                                          [[maybe_unused]] profiling::AnyInputType allowedTypes)
{
    if (superType == type) {
        return (superAllowedTypes & allowedTypes) == allowedTypes ? std::optional {true} : std::nullopt;
    }

    switch (superType) {
        case ark::compiler::AnyBaseType::ECMASCRIPT_OBJECT_TYPE:
            switch (type) {
                case ark::compiler::AnyBaseType::ECMASCRIPT_HEAP_OBJECT_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_STRING_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_SYMBOL_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_BIGINT_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_ARRAY_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_TRANSITION_HANDLER_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_PROTOTYPE_HANDLER_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_SPECIAL_INDEXED_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_FUNCTION_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_CALLABLE_TYPE:
                    return true;
                default:
                    break;
            }
            break;
        case ark::compiler::AnyBaseType::ECMASCRIPT_HEAP_OBJECT_TYPE:
            switch (type) {
                case ark::compiler::AnyBaseType::ECMASCRIPT_STRING_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_SYMBOL_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_BIGINT_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_ARRAY_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_TRANSITION_HANDLER_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_PROTOTYPE_HANDLER_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_SPECIAL_INDEXED_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_FUNCTION_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_CALLABLE_TYPE:
                    return true;
                default:
                    break;
            }
            break;
        case ark::compiler::AnyBaseType::ECMASCRIPT_STRING_TYPE:
        case ark::compiler::AnyBaseType::ECMASCRIPT_SYMBOL_TYPE:
        case ark::compiler::AnyBaseType::ECMASCRIPT_BIGINT_TYPE:
        case ark::compiler::AnyBaseType::ECMASCRIPT_ARRAY_TYPE:
        case ark::compiler::AnyBaseType::ECMASCRIPT_TRANSITION_HANDLER_TYPE:
        case ark::compiler::AnyBaseType::ECMASCRIPT_PROTOTYPE_HANDLER_TYPE:
            switch (type) {
                case ark::compiler::AnyBaseType::ECMASCRIPT_OBJECT_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_HEAP_OBJECT_TYPE:
                    return std::nullopt;
                default:
                    break;
            }
            break;
        case ark::compiler::AnyBaseType::ECMASCRIPT_SPECIAL_INDEXED_TYPE:
            switch (type) {
                case ark::compiler::AnyBaseType::ECMASCRIPT_OBJECT_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_HEAP_OBJECT_TYPE:
                    return std::nullopt;
                case ark::compiler::AnyBaseType::ECMASCRIPT_STRING_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_SYMBOL_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_BIGINT_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_TRANSITION_HANDLER_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_PROTOTYPE_HANDLER_TYPE:
                    return true;
                default:
                    break;
            }
            break;
        case ark::compiler::AnyBaseType::ECMASCRIPT_FUNCTION_TYPE:
        case ark::compiler::AnyBaseType::ECMASCRIPT_CALLABLE_TYPE:
            switch (type) {
                case ark::compiler::AnyBaseType::ECMASCRIPT_OBJECT_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_HEAP_OBJECT_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_CALLABLE_TYPE:
                case ark::compiler::AnyBaseType::ECMASCRIPT_FUNCTION_TYPE:
                    return std::nullopt;
                default:
                    break;
            }
            break;
        case ark::compiler::AnyBaseType::UNDEFINED_TYPE:
            return true;
        default:
            break;
    }
    if (type == ark::compiler::AnyBaseType::UNDEFINED_TYPE) {
        return std::nullopt;
    }
    if (superType == ark::compiler::AnyBaseType::ECMASCRIPT_INT_TYPE ||
        superType == ark::compiler::AnyBaseType::ECMASCRIPT_DOUBLE_TYPE ||
        type == ark::compiler::AnyBaseType::ECMASCRIPT_INT_TYPE ||
        type == ark::compiler::AnyBaseType::ECMASCRIPT_DOUBLE_TYPE) {
        auto possibleMaskSuper = GetPossibleTypeMask(superType, superAllowedTypes);
        auto possibleMask = GetPossibleTypeMask(type, allowedTypes);
        if ((possibleMaskSuper & possibleMask) == possibleMask) {
            return true;
        }
        if ((possibleMaskSuper & possibleMask) != 0) {
            return std::nullopt;
        }
    }
    return false;
}
}  // namespace ark::compiler::ecmascript

#endif  // PLUGINS_ECMASCRIPT_COMPILER_OPTIMIZER_IR_DYN_DATATYPE_H
